package com.kayak.kboot.dao;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
import com.kayak.kboot.dao.kSql.object.SqlCheck;
import com.kayak.kboot.dao.kSql.object.SqlConfig;
import com.kayak.kboot.dao.kSql.object.SqlConfigFile;
import com.kayak.kboot.dao.kSql.object.SqlQueryTree;
import com.kayak.kboot.dao.kSql.object.SqlTransfer;
import com.kayak.kboot.web.sys.IReloadConfig;
import com.kayak.kboot.web.sys.SysBeans;
import com.kayak.kkutils.file.FileUtil;
import com.kayak.kkutils.Tools;
import com.kayak.kkutils.XmlUtil;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;

@Component("sysSql")
public class SqlManager implements ServletContextAware,IReloadConfig {

	private static Logger				log					= LoggerFactory.getLogger(SqlManager.class);

	
	private GeneralCacheAdministrator	oscache=new GeneralCacheAdministrator();


	/**
	 * 要读取的SQL配置文件集合
	 */
	private String				configFiles			= "/conf/sql";

	/**
	 * 定义SQL配置对象保存到缓存中的KEY前缀
	 */
	private static final String			SQLINFO_KEY_PREFIX	= "SQLCONFIG.";


	/**
	 * 已读取的配置文件与sqlid对应关系<br />
	 * 以每个页面配置文件的名称作为key，保存此配配置文件中有哪些sqlid定义在这里
	 */
	private static List<SqlConfigFile>	sqlinfoFiles;
	/**
	 * 已读取的配置文件与exeid对应关系<br />
	 * 以每个面板配置文件的名称作为key，保存此配配置文件中有哪些exeid定义在这里
	 */
	private static List<SqlConfigFile>	executeFiles;

	/**
	 * 用于在加载配置时保存已加载的sqlid值
	 */
	private static List<String>			loadedSqlids;

	/**
	 * 用于在加载配置时保存已加载的exeid值
	 */
	private static List<String>			loadedExeids;

	@PostConstruct	
	public void init() {

		// 清缓存
		this.oscache.flushAll();

		if (loadedSqlids == null)
			loadedSqlids = new ArrayList<String>();
		else
			loadedSqlids.clear();

		if (sqlinfoFiles == null)
			sqlinfoFiles = new ArrayList<SqlConfigFile>();
		else
			sqlinfoFiles.clear();

		if (loadedExeids == null)
			loadedExeids = new ArrayList<String>();
		else
			loadedExeids.clear();

		if (executeFiles == null)
			executeFiles = new ArrayList<SqlConfigFile>();
		else
			executeFiles.clear();

		reload();
	}

	public void reload() {
		// 读取配置文件内容
		if (this.configFiles == null || this.configFiles.isEmpty()) {
			log.error("没有指定SQL配置文件（sysSql bean 的 configFiles 属性）");
			return;
		}

		// 先加载所有SQL-INFO配置
		loadSqlInfo(configFiles);
	}

	public void destroy() {
		this.oscache.destroy();
	}

	private String loadSqlInfo(String filename) {
		log.debug("加载SQL-INFO配置：" + filename);
		File f = FileUtil.getFile(filename, null);
		if (f == null) {
			String res = "找不到文件：" + filename;
			log.error(res);
			return res;
		}
		return loadConfig(f, true);
	}


	/**
	 * 加载SQL配置文件
	 *
	 * @param file
	 * @param loadSqlinfo
	 *            true则加载SQLINFO配置，false则加载SQLEXECUTE配置
	 * @return
	 */
	private String loadConfig(File file, boolean loadSqlinfo) {
		String res;
		String path = file.getAbsolutePath();
		if (!file.exists()) {
			res = "SQL配置文件或目录不存在：" + path;
			log.error(res);
			return res;
		}
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			List<String> lst = new ArrayList<String>();
			for (File f : files) {
				String ret = loadConfig(f, loadSqlinfo);
				if (!Tools.strIsEmpty(ret))
					lst.add(ret);
			}
			return Tools.listJoin(lst, "\n");
		} else {
			return loadSqlInfoFile(file);
		}
	}


	/**
	 * 添加 SQL-INFO 配置缓存
	 *
	 * @param config
	 */
	public void putSqlConfig(SqlConfig config) {
		if (config == null)
			return;

		String sqlid = config.getSqlid();

		if (Tools.strIsEmpty(sqlid)) {
			log.error("添加 SqlConfig 对象到缓存出错：sqlid不能为空");
			return;
		}
		if (loadedSqlids.contains(genSqlid(sqlid))) {
			log.error("添加 SqlConfig 对象到缓存出错：sqlid=" + genSqlid(sqlid)
					+ " 的SQL-INFO配置已存在");
			return;
		}
		putSqlConfig(genSqlid(sqlid), config);
		loadedSqlids.add(genSqlid(sqlid));
	}

	/**
	 * 添加SqlConfig对象到缓存
	 *
	 * @param sqlid
	 * @param config
	 */
	private void putSqlConfig(String sqlid, SqlConfig config) {
		this.oscache.putInCache(sqlInfoCacheKey(sqlid), config);
	}

	public static String sqlInfoCacheKey(String sqlid) {
		return SQLINFO_KEY_PREFIX + sqlid;
	}



	/**
	 * 获取缓存的SqlConfig对象
	 *
	 * @param sqlid
	 *            SQLINFO中的id
	 * @return
	 */
	private SqlConfig getSqlConfig(String sqlid) {
		String key = sqlInfoCacheKey(genSqlid(sqlid));
		try {
			SqlConfig obj = (SqlConfig) this.oscache.getFromCache(key);

			log.debug("从缓存中取得：" + key);

			return obj;
		} catch (NeedsRefreshException e) {
			this.oscache.cancelUpdate(key);
			// log.warn(e.getMessage(), e);
			// return (SqlConfig) e.getCacheContent();
			return null;
		}
	}







	/**
	 * 添加sqlid与filename配置文件的关联
	 */
	private static void add2Sqlinfofiles(SqlConfigFile file, String sqlid) {
		List<String> ids = file.getIds();
		if (ids == null) {
			ids = new ArrayList<String>();
			file.setIds(ids);
		}
		if (!ids.contains(sqlid)) {
			ids.add(sqlid);
		}
	}


	/**
	 * 加载sql-info配置文件，根据每个sqlinfo配置生成SqlConfig
	 */
	public String loadSqlInfoFile(String filename) {
		File f = FileUtil.getFile(filename, null);
		if (f == null) {
			String res = "找不到文件：" + filename;
			log.error(res);
			return res;
		}
		return loadSqlInfoFile(f);
	}

	/**
	 * 加载sql-info配置文件，根据每个sqlinfo配置生成SqlConfig
	 */
	public String loadSqlInfoFile(File file) {
		if (file == null)
			return null;

		String filename = file.getName();
		if (!// 只加载sql-开头，-info结尾的xml文件
		(filename.startsWith("sql-")
				&& FilenameUtils.getBaseName(filename).endsWith("-info") && FilenameUtils
				.getExtension(filename).equalsIgnoreCase("xml"))) {// 不是SQLINFO配置文件则不加载
			return null;
		}

		String filepath = file.getAbsolutePath();

		SqlConfigFile confFile = SqlConfigFile.find(sqlinfoFiles, filepath);
		if (confFile == null) {
			confFile = new SqlConfigFile(filepath, file.lastModified());
			sqlinfoFiles.add(confFile);
		} else {
			long lastModified = file.lastModified();
			if (lastModified == confFile.getLastModified())
				// 文件没有更新，不需要重新加载
				return "";
		}

		String res;

		Element root = null;// 引用读出的配置文件的根节点
		try {
			// 读取XML配置文件
			SAXReader saxReader = new SAXReader();// 创建读取xml文件的对象
			Document doc = saxReader.read(file);// 读取xml文件
			root = doc.getRootElement();// 取根节点
		} catch (DocumentException e) {
			res = "打开配置文件失败：" + filepath;
			log.error(res);
			e.printStackTrace();
			return res;
		}

		confFile.setLastModified(file.lastModified());


		if (root == null) {// 没有根节点，直接退出
			res = "配置文件没有配置信息：" + filepath;
			log.info(res);
			return res;
		}

		// 取配置节点集合
		@SuppressWarnings("rawtypes")
		Iterator configs = root.elementIterator();
		if (configs == null) {// 没有配置节点，直接退出
			res = "配置文件没有配置信息：" + filepath;
			log.info(res);
			return res;
		}

		int n = 0;// sqlinfo节点数累计变量
		while (configs.hasNext()) {
			n++;// sqlinfo节点数累计，用以获取当前加载第几个节点
			// 取得sqlinfo节点对象
			Element sqlinfoEl = (Element) configs.next();
			Element el;

			// 定义构造SqlConfig对象所需变量
			String sqlid = null, sqlname = null;
			List<SqlCheck> checks = null;
			List<SqlTransfer> transfers = null;
			SqlQueryTree querytree = null;

			// 从配置节点中获取信息
			sqlid = XmlUtil.getAttributeString(sqlinfoEl, "sqlid");

			if (Tools.strIsEmpty(sqlid)) {
				log.error("加载SQL配置文件" + filename + "时出错：第" + n
						+ "个sqlinfo节点的sqlid属性为空或未指定");
				continue;
			}
			if (loadedSqlids.contains(genSqlid(sqlid))) {
				log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
						+ "的SQL配置重复，第" + n + "个sqlinfo节点的配置将不会被加载");
				continue;
			}

			sqlname = XmlUtil.getElementTextTrim(sqlinfoEl.element("sqlname"));
			if (Tools.strIsEmpty(sqlname)) {
				log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
						+ "的sqlinfo > sqlname节点未指定或值为空");
				continue;
			}


			// 读取SQL执行前校验配置信息
			el = sqlinfoEl.element("checks");
			if (el != null) {// 存在checks执行前校验配置
				@SuppressWarnings("rawtypes")
				Iterator checkIt = el.elementIterator("check");
				if (checkIt != null) {
					checks = new ArrayList<SqlCheck>();// 用于保存读取到的配置信息
					int nc = 0;// 用于计数，表示读取到第几个check节点配置
					while (checkIt.hasNext()) {
						nc++;
						Element checkEl = (Element) checkIt.next();
						String checkname = null, checksql = null, checksqlid = null, checkstring = null, compareval = null, comparesign = null, errtext = null;
						Boolean exitall = null;

						checkname = XmlUtil.getElementTextTrim(checkEl
								.element("checkname"));
						checksql = XmlUtil.getElementTextTrim(checkEl
								.element("checksql"));
						List<Element> sqlEls = checkEl.elements("checksql");

						checksqlid = XmlUtil.getElementTextTrim(checkEl
								.element("checksqlid"));
						checkstring = XmlUtil.getElementTextTrim(checkEl
								.element("checkstring"));
						if (Tools.strIsEmpty(checksql)
								&& Tools.strIsEmpty(checksqlid)
								&& Tools.strIsEmpty(checkstring)) {
							log.error("加载SQL配置文件"
									+ filename
									+ "时出错：sqlid="
									+ sqlid
									+ "的sqlinfo > checks的第"
									+ nc
									+ "个check节点中必须指定checksql/checksqlid/checkstring其中一个作为子节点");
							continue;
						}
						compareval = XmlUtil.getElementTextTrim(checkEl
								.element("compareval"));
						if (Tools.strIsEmpty(compareval)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > checks的第" + nc
									+ "个check > compareval节点未指定或值为空");
							continue;
						}
						comparesign = XmlUtil.getAttributeString(checkEl,
								"comparesign", "eql");
						if (Tools.strIsEmpty(comparesign)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > checks的第" + nc
									+ "个check节点的comparesign属性为空或未指");
							continue;
						}
						errtext = XmlUtil.getElementTextTrim(checkEl
								.element("errtext"));
						if (Tools.strIsEmpty(compareval)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > checks的第" + nc
									+ "个check > errtext节点未指定或值为空");
							continue;
						}
						exitall = XmlUtil.getAttributeBoolean(checkEl,
								"exitall", true);
						SqlCheck chk = new SqlCheck(checkname, checksql,
								checksqlid, checkstring, compareval,
								comparesign, errtext, exitall);
						if (sqlEls != null) {
							for (Element e : sqlEls) {
								String dialect = XmlUtil.getAttributeString(e,
										"dialect");
								chk.addCheckSql(XmlUtil.getElementTextTrim(e),
										dialect);
							}
						}
						checks.add(chk);
					}
				}
			}

			// 读取SQL查询结果字段字典转换配置信息
			el = sqlinfoEl.element("transfers");
			if (el != null) {// 存在transfers查询结果字典转换配置
				@SuppressWarnings("rawtypes")
				Iterator transferIt = el.elementIterator("transfer");
				if (transferIt != null) {
					transfers = new ArrayList<SqlTransfer>();// 用于保存读取到的配置信息
					int nc = 0;// 用于计数，表示读取到第几个transfer节点配置
					while (transferIt.hasNext()) {
						nc++;
						Element transferEl = (Element) transferIt.next();
						String column = null, target = null, dict = null;

						column = XmlUtil.getAttributeString(transferEl,
								"column");
						if (Tools.strIsEmpty(column)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > transfers的第" + nc
									+ "个transfer节点的column属性为空或未指");
							continue;
						}
						target = XmlUtil.getAttributeString(transferEl,
								"target");
						if (Tools.strIsEmpty(target)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > transfers的第" + nc
									+ "个transfer节点的target属性为空或未指");
							continue;
						}
						dict = XmlUtil.getAttributeString(transferEl, "dict");
						if (Tools.strIsEmpty(dict)) {
							log.error("加载SQL配置文件" + filename + "时出错：sqlid="
									+ sqlid + "的sqlinfo > transfers的第" + nc
									+ "个transfer节点的dict属性为空或未指");
							continue;
						}
						transfers.add(new SqlTransfer(column, target, dict));
					}
				}
			}

			el = sqlinfoEl.element("querytree");
			if (el != null) {// 存在查询结果树结构定义配置
				String idcolumn = null, textcolumn = null, iconClscolumn = null, iconcolumn = null, diffway = null, diffcondition = null, initexpand = null;
				Boolean isasync = null;
				idcolumn = XmlUtil.getAttributeString(el, "idcolumn");
				if (Tools.strIsEmpty("idcolumn")) {
					log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
							+ "的sqlinfo > querytree节点的idcolumn属性为空或未指定");
					continue;
				}
				textcolumn = XmlUtil.getAttributeString(el, "textcolumn");
				if (Tools.strIsEmpty("textcolumn")) {
					log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
							+ "的sqlinfo > querytree节点的textcolumn属性为空或未指定");
					continue;
				}
				diffway = XmlUtil.getAttributeString(el, "diffway", "upper");
				if (Tools.strIsEmpty("diffway")) {
					log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
							+ "的sqlinfo > querytree节点的diffway属性为空或未指定");
					continue;
				}
				diffcondition = XmlUtil.getAttributeString(el, "diffcondition");
				if (Tools.strIsEmpty("diffcondition")) {
					log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
							+ "的sqlinfo > querytree节点的diffcondition属性为空或未指定");
					continue;
				}
				iconClscolumn = XmlUtil.getAttributeString(el, "iconClscolumn");
				iconcolumn = XmlUtil.getAttributeString(el, "iconcolumn");
				initexpand = XmlUtil.getAttributeString(el, "initexpand",
						"none");
				isasync = XmlUtil.getAttributeBoolean(el, "isasync", false);
				querytree = new SqlQueryTree(idcolumn, textcolumn,
						iconClscolumn, iconcolumn, diffway, diffcondition,
						initexpand, isasync);
			}

			if (checks != null && checks.isEmpty())
				checks = null;
			if (transfers != null && transfers.isEmpty())
				transfers = null;

			SqlConfig conf = new SqlConfig(sqlid, sqlname, checks, transfers,
					querytree);
			// sql结点可出现多个，实现不同数据库的sql语句
			List<Element> sqlList = sqlinfoEl.elements("sql");

			if (sqlList == null || sqlList.size() < 1) {
				log.error("加载SQL配置文件" + filename + "时出错：sqlid=" + sqlid
						+ "的sqlinfo > sql节点未指定或值为空");
				continue;
			}
			if (sqlList != null) {
				for (Element elSql : sqlList) {
					String sql = XmlUtil.getElementTextTrim(elSql);
					String dialect = XmlUtil.getAttributeString(elSql,
							"dialect");
					conf.addSql(sql, dialect);
				}
			}

			// 添加到缓存
			this.putSqlConfig(conf);
			// 收集已加载的sqlid
			if (!loadedSqlids.contains(genSqlid(sqlid)))
				loadedSqlids.add(genSqlid(sqlid));

			// 与文件名称关联
			add2Sqlinfofiles(confFile, genSqlid(sqlid));
		}
		res = "加载配置文件完成：" + filepath;
		log.info(res);
		return res;
	}

	/**
	 *
	 * @param sqlid
	 * @return
	 */
	public static String genSqlid(String sqlid) {
		return sqlid;
	}


	/**
	 * 加载sql-execute配置文件，根据配置生成exeid与多SqlConfig对象集合的Map
	 */
	public static SqlConfig getSqlid(String sqlid) {
		SqlManager sysSql = SysBeans.getSysSql();

		sqlid = Tools.trimString(sqlid);
		SqlConfig config = sysSql.getSqlConfig(sqlid);
		if (config == null) {
			log.error("找不到sqlid=" + sqlid + "的SQL配置信息");
		}
		return config;
	}

	/**
	 * 获取已加载的sqlid集合
	 */
	public static List<String> getSqlids() {
		return loadedSqlids;
	}

	/**
	 * 获取已加载的exeid集合
	 */
	public static List<String> getExeids() {
		return loadedExeids;
	}

	/**
	 * 获取已加载的SQL配置文件集合
	 */
	public static List<String> getLoadedSqlinfoFiles() {
		List<String> list = new ArrayList<String>();
		list.addAll(SqlConfigFile.listFilenames(sqlinfoFiles));
		return list;
	}

	/**
	 * 获取已加载的SQL执行配置文件集合
	 */
	public static List<String> getLoadedExecuteFiles() {
		List<String> list = new ArrayList<String>();
		list.addAll(SqlConfigFile.listFilenames(executeFiles));
		return list;
	}

	



	/**
	 * 提供给SPRING注入的方法
	 *
	 * @param oscache
	 */
	public void setOscache(GeneralCacheAdministrator oscache) {
		this.oscache = oscache;
	}


	/**
	 * 已读取的配置文件与sqlid对应关系<br />
	 * 以每个页面配置文件的名称作为key，保存此配配置文件中有哪些sqlid定义在这里
	 *
	 * @return
	 */
	public static List<SqlConfigFile> getSqlinfoFiles() {
		return sqlinfoFiles;
	}

	public boolean isInited() {
		return true;
	}
	
	/**
	 * 这个对象由SPRING自动注入，在这个类里需要使用这个对象来获取配置文件的实际路径
	 */
	private ServletContext	servletContext;

	/**
	 * 提供给SPRING注入的方法
	 */
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}
}
